using System;
using System.Collections.Generic;
using System.IO;
using System.Security;
using System.Security.Cryptography;
using System.Text;

namespace Universe.Farm.Service
{
    class BufferProtocol : IBufferProtocol
    {
        CommunicationParameters.CommunicationIntegrityAlgorithm _alg = CommunicationParameters.CommunicationIntegrityAlgorithm.MD5;

        private static readonly Dictionary<CommunicationParameters.CommunicationIntegrityAlgorithm, HashAlgorithm> Algorithms;

        private static byte[] Silk = Encoding.ASCII.GetBytes("Server Farm");

        static BufferProtocol()
        {
            Algorithms = new Dictionary<CommunicationParameters.CommunicationIntegrityAlgorithm, HashAlgorithm>();
            Algorithms[CommunicationParameters.CommunicationIntegrityAlgorithm.MD5] = HashAlgorithm.Create("MD5");
            Algorithms[CommunicationParameters.CommunicationIntegrityAlgorithm.SHA1] = HashAlgorithm.Create("SHA1");
            Algorithms[CommunicationParameters.CommunicationIntegrityAlgorithm.SHA256] = HashAlgorithm.Create("SHA256");
            Algorithms[CommunicationParameters.CommunicationIntegrityAlgorithm.SHA384] = HashAlgorithm.Create("SHA384");
            Algorithms[CommunicationParameters.CommunicationIntegrityAlgorithm.SHA512] = HashAlgorithm.Create("SHA512");
        }


        public BufferProtocol(CommunicationParameters.CommunicationIntegrityAlgorithm algorithm)
        {
            _alg = algorithm;
        }

        public byte[] Encode(byte[] bytes)
        {
            using (MemoryStream mem = new MemoryStream())
            {
                mem.WriteByte((byte) _alg);
                HashAlgorithm ha;
                if (Algorithms.TryGetValue(_alg, out ha))
                {
                    byte[] hash = GetHash(ha, bytes);
                    mem.Write(hash, 0, hash.Length);
                }

                mem.Write(bytes, 0, bytes.Length);
                return mem.ToArray();
            }
        }

        public byte[] Decode(byte[] bytes)
        {
            if (bytes.Length == 0)
                throw new VerificationException();

            CommunicationParameters.CommunicationIntegrityAlgorithm a = (CommunicationParameters.CommunicationIntegrityAlgorithm) bytes[0];
            HashAlgorithm ha;
            if (Algorithms.TryGetValue(a, out ha))
            {
                int size = ha.HashSize / 8;
                if (size + 1 > bytes.Length)
                    throw new VerificationException();

                byte[] ret = new byte[bytes.Length - 1 - size];
                for (int i = 0; i < ret.Length; i++)
                    ret[i] = bytes[i + size + 1];

                byte[] realHash = GetHash(ha, ret);
                for (int i = 0; i < size; i++)
                {
                    byte stored = bytes[i + 1], real = realHash[i];
                    if (stored != real)
                        throw new VerificationException();
                }

                return ret;
            }
            else
            {
                const int size = 0;
                byte[] ret = new byte[bytes.Length - 1 - size];
                for (int i = 0; i < ret.Length; i++)
                    ret[i] = bytes[i + size + 1];

                return ret;
            }
        }

        static readonly object Sync = new object();
        static byte[] GetHash(HashAlgorithm ha, byte[] arg)
        {
            lock (Sync)
            {
                byte[] bytes = new byte[Silk.Length + arg.Length];
                Silk.CopyTo(bytes, 0);
                if (arg.Length > 0)
                    arg.CopyTo(bytes, Silk.Length);

                return ha.ComputeHash(bytes);
            }
        }


    }
}
